#!/usr/bin/perl

use strict;
use Device::SerialPort;
use Time::HiRes qw (time usleep);

my $if0 = new Device::SerialPort('/dev/ttyS0', 0);

$if0->baudrate(19200);
$if0->parity("odd");
$if0->databits(8);
$if0->stopbits(2);
$if0->handshake('none');

die "$!" unless $if0->write_settings;

my $if1 = new Device::SerialPort('/dev/ttyS1', 0);

$if1->baudrate(19200);
$if1->parity("odd");
$if1->databits(8);
$if1->stopbits(2);
$if1->handshake('none');

die "$!" unless $if1->write_settings;

# sensor reading blocky things.
my @sensors1 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,16);
my @sensors0a = (17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,32);
my @sensors0b = (33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,48);

my %sensor_addresses = (
			1 => 0,
			2 => 1,
			3 => 2,
			4 => 3,
			5 => 4,
			6 => 5,
			7 => 6,
			8 => 7,
			9 => 8,
			10 => 9,
			11 => 10,
			12 => 11,
			13 => 12,
			14 => 13,
			15 => 14,
			16 => 15,

			17 => 0,
			18 => 1,
			19 => 2,
			20 => 3,
			21 => 4,
			22 => 5,
			23 => 6,
			24 => 7,
			25 => 8,
			26 => 9,
			27 => 10,
			28 => 11,
			29 => 12,
			30 => 13,
			31 => 14,
			32 => 15,

			33 => 16,
			34 => 17,
			35 => 18,
			36 => 19,
			37 => 20,
			38 => 21,
			39 => 22,
			40 => 23,
			41 => 24,
			42 => 25,
			43 => 26,
			44 => 27,
			45 => 28,
			46 => 29,
			47 => 30,
			48 => 31,
			);

my %sensor_groups = (
		     1 => [1,17,18],
		     2 => [2,19,20],
		     3 => [3,21,22],
		     4 => [4,23,24],
		     5 => [5,25,26],
		     6 => [6,27,28],
		     7 => [7,29,30],
		     8 => [8,31,32],
		     9 => [9,41,42],
		     10 => [10,35,36],
		     11 => [11,37,38],
		     12 => [12,39,40],
		     13 => [13,33,34],
		     14 => [14,43,44],
		     15 => [15,45,46],
		     16 => [16,47,48],
		     );

# invert the sensor_groups hash for quick lookups.
my %sensor_group_map = map { 
    my $group = $_;
    map { ($_ => $group); } @{$sensor_groups{$group}};
} sort keys %sensor_groups;

my %effect_addresses = (
		     1 => [32,33,34,35],
		     2 => [36,37,38,39],
		     3 => [40,41,42,43],
		     4 => [44,45,46,47],
		     5 => [48,49,50,51],
		     6 => [52,53,54,55],
		     7 => [56,57,58,59],
		     8 => [60,61,62,63],
		     9 => [64,65,66,67],
		     10 => [68,69,70,71],
		     11 => [72,73,74,75],
		     12 => [76,77,78,79],
		     13 => [80,81,82,83],
		     14 => [84,85,86,87],
		     15 => [88,89,90,91],
		     16 => [92,93,94,95],
		     );

# for storing the current state of each effect.
my %effect_state;

# store the sensor's current value.
my %sensor_current;

# calibrate the sensors.
my %baseline;
my %baseline_values;
$| = 1;
print "Calibrating...\n";
calibrate($if0, @sensors0a);
print "uno!\n";
calibrate($if0, @sensors0b);
print "dos!\n";
calibrate($if1, @sensors1);
print "tres!\n";


foreach my $sensor (sort { $a <=> $b } keys %baseline_values) {
  my $sum;
  foreach(@{$baseline_values{$sensor}}) {
    $sum += $_;
  }
  
  $baseline{$sensor} = $sum / scalar(@{$baseline_values{$sensor}});
  print "$sensor: $baseline{$sensor}\n";
}

$baseline{5} += 6;
$baseline{12} += 6;
$baseline{6} += 6;
$baseline{2} += 6;
$baseline{29} += 6;
#$baseline{2} += 6;

print "\nDone.\n";

my $last_time = time();
my %timed;
my $exit = 0;
my @to_write;
my $last_batch = 'b';
while(!$exit) {
  my @read_sensors0;
  my @read_sensors1 = @sensors1;
  
  # jankity...
  if($last_batch eq 'a') {
    @read_sensors0 = @sensors0b;
    $last_batch = 'b';
  } else {
    @read_sensors0 = @sensors0a;
    $last_batch = 'a';
  }

  # translate sensor ids into sensor addresses.
  my @write_sensors0 = map { $sensor_addresses{$_} } @read_sensors0;
  my @write_sensors1 = map { $sensor_addresses{$_} } @read_sensors1;
    
  # write whatever needs it between read cycles
  if(@to_write) {
    print "write: " . join(',', @to_write) . "\n";
    $if0->write(pack('C*', @to_write));
    $if0->read(255);
    undef @to_write;
  }

  # send the reads.
  $if0->write(pack('C*', @write_sensors0));
  $if1->write(pack('C*', @write_sensors1));

  pop @read_sensors0; #remove that last discarded value.
  pop @read_sensors1;

  my $first0 = 1;
  my $first1 = 1;
  
  while(@read_sensors0 || @read_sensors1) {
    my ($count0, $raw_read0) = $if0->read(255);
    my ($count1, $raw_read1) = $if1->read(255);

    next unless ($count1 || $count0);

    # jankity...
    my $pick = 0;
    foreach my $read ($raw_read0, $raw_read1) {
      foreach my $value (unpack('C*', $read)) {
        my $curr;
        if($pick == 0) {
          if($first0) {
            undef $first0;
            next;
          }

          $curr = shift(@read_sensors0);
        } else {
          if($first1) {
            undef $first1;
            next;
          }

          $curr = shift(@read_sensors1);
        }

        push @{$timed{$curr}}, { val => $value, when => time() };
#        print $value . "\n" if $curr == 23;

        # trim @timed
        if(scalar(@{$timed{$curr}}) > 35) {
          shift(@{$timed{$curr}});
        } else {
          next;
        }
      
        # average them all.
        my $sum;
        my $first_time = $timed{$curr}->[0]->{when};
        my $last_time;
        my $avg_count;
        foreach(@{$timed{$curr}}) {
          if($_->{val} > ($baseline{$curr} + 3) && $_->{val} < ($baseline{$curr} + 25) ) {
            # yay!
#            do {
#              #print "skipped $_->{val} ($baseline{$curr})\n";
#              next;
#            } unless (
#              scalar(
#                grep { 
#                  ($_ > $baseline{$curr}) + 3 && ($_ < $baseline{$curr} + 25)
#                } map { $_->{val} } @{$timed{$curr}}
#              ) > 3
#            );

            unless(
              (($_->{val} > $timed{$curr}->[$avg_count - 1]->{val}) + 3 && $_->{val} < ($timed{$curr}->[$avg_count - 1]->{val} + 25)) ||
              (($_->{val} > $timed{$curr}->[$avg_count + 1]->{val}) + 3 && $_->{val} < ($timed{$curr}->[$avg_count + 1]->{val} + 25))
            ) {
#              print "skipped $_->{val} ($baseline{$curr})\n";
              next;
            }
          }
          $sum += $_->{val};
          $last_time = $_->{when};
          $avg_count++;
        }

        my $avg;
        if($sum) {
          $avg = $sum / $avg_count;
        } else {
          $avg = 0;
        }
#        print "$curr: avg: $avg, count: " . scalar(@{$timed{$curr}}) . "\n" if $curr == 23;

        $sensor_current{$curr} = $avg;

        # get the highest average in the group.
#        my @current = map { $sensor_current{$_} } @{$sensor_groups{$sensor_group_map{$curr}}};
#        my $measure = (sort { $b <=> $a } @current)[0];
        my $measure = $avg;
        
#        next if $avg > 75;  #skip the ones that aren't plugged in.
#        print "current: " . join(',', @current) . "\n";
        
        if($measure > $baseline{$curr} + 40) {
          print "low: $pick / $curr: avg: $measure, count: " . scalar(@{$timed{$curr}}) . ", time: " . sprintf('%.4f', ($last_time - $first_time)) . ", diff: " . ($measure - $baseline{$curr}) . ", baseline: $baseline{$curr}\n";
#          print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
          fire_the_fucking_flamethrowers_oh_my_god($sensor_group_map{$curr}, 'low');
        } elsif($measure > $baseline{$curr} + 40) {
          # was 11.5
          print "high: $pick / $curr: avg: $measure, count: " . scalar(@{$timed{$curr}}) . ", time: " . sprintf('%.4f', ($last_time - $first_time)) . ", diff: " . ($measure - $baseline{$curr}) . ", baseline: $baseline{$curr}\n";
#          print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
          fire_the_fucking_flamethrowers_oh_my_god($sensor_group_map{$curr}, 'high');
        } else {
#          print "off: $pick / $curr: avg: $measure, count: " . scalar(@{$timed{$curr}}) . ", time: " . sprintf('%.4f', ($last_time - $first_time)) . ", diff: " . ($measure - $baseline{$curr}) . ", baseline: $baseline{$curr}\n" if $avg > 45;
#          print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
          fire_the_fucking_flamethrowers_oh_my_god($sensor_group_map{$curr}, 'off');
        }
      }
      $pick = 1;
    }
  }
}

sub fire_the_fucking_flamethrowers_oh_my_god {
  my($effect, $state) = @_;

  # effect array offsets:
  # 0 - high off
  # 1 - high on
  # 2 - low off
  # 3 - low on
  
  if($effect_state{$effect} eq 'off') {
    if($state eq 'low') {
      push @to_write, $effect_addresses{$effect}->[3];
    } elsif($state eq 'high') {
      push @to_write, $effect_addresses{$effect}->[1];
    } else {
      # already off.
      #print "off from off\n";
    }
  } elsif($effect_state{$effect} eq 'low') {
    # low effect is on.
    if($state eq 'low') {
      # do nothing.
    } elsif($state eq 'high') {
      push @to_write, $effect_addresses{$effect}->[2];
      push @to_write, $effect_addresses{$effect}->[1];
    } else {
#      print "off from high\n";
      push @to_write, $effect_addresses{$effect}->[2];
    }
  } elsif($effect_state{$effect} eq 'high') {
    # high effect is on.
    if($state eq 'low') {
      push @to_write, $effect_addresses{$effect}->[0];
      push @to_write, $effect_addresses{$effect}->[3];
    } elsif($state eq 'high') {
      # do nothing.
    } else {
#      print "off from low\n";
      push @to_write, $effect_addresses{$effect}->[0];
    }
  }

  $effect_state{$effect} = $state;
}

sub calibrate {
  my ($if, @sensors) = @_;
  foreach(1..30) {
    my @read_sensors = @sensors;
    my @write_sensors = map { $sensor_addresses{$_} } @sensors;
    my $first = 1;

    $if->write(pack('C*', @write_sensors));
    usleep(100000);
    pop @read_sensors;

    while(@read_sensors) {
      my ($count, $raw_read) = $if->read(255);
      foreach my $value (unpack('C*', $raw_read)) {
        if($first) {
          undef $first;
          next;
        }
        my $sensor = shift @read_sensors;
        push @{$baseline_values{$sensor}}, $value;
        print ".";
      }
    }
  }
}
